/***********************************************************************
    filename:   CEGUIRenderedStringComponent.cpp
    created:    24/05/2009
    author:     Paul Turner
 *************************************************************************/

#include "hgeGuiRenderedStringComponent.h"
#include "hgeExceptions.h"
#include "hgeGuiSystem.h"
#include "hgefont.h"
#include "hgeFontManager.h"
#include "hgeImage.h"
#include "hgeTextUtils.h"
#include "hgeGuiWindowManager.h"

//----------------------------------------------------------------------------//
RenderedStringComponent::RenderedStringComponent() :
    d_padding(0, 0, 0, 0),
    d_verticalFormatting(VF_BOTTOM_ALIGNED),
    d_aspectLock(false)
{
}

//----------------------------------------------------------------------------//
RenderedStringComponent::~RenderedStringComponent()
{
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setVerticalFormatting(VerticalFormatting fmt)
{
    d_verticalFormatting = fmt;
}

//----------------------------------------------------------------------------//
VerticalFormatting RenderedStringComponent::getVerticalFormatting() const
{
    return d_verticalFormatting;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setPadding(const hgeRect& padding)
{
    d_padding = padding;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setLeftPadding(const float padding)
{
    d_padding.d_left = padding;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setRightPadding(const float padding)
{
    d_padding.d_right = padding;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setTopPadding(const float padding)
{
    d_padding.d_top = padding;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setBottomPadding(const float padding)
{
    d_padding.d_bottom = padding;
}

//----------------------------------------------------------------------------//
const hgeRect& RenderedStringComponent::getPadding() const
{
    return d_padding;
}

//----------------------------------------------------------------------------//
float RenderedStringComponent::getLeftPadding() const
{
    return d_padding.d_left;
}

//----------------------------------------------------------------------------//
float RenderedStringComponent::getRightPadding() const
{
    return d_padding.d_right;
}

//----------------------------------------------------------------------------//
float RenderedStringComponent::getTopPadding() const
{
    return d_padding.d_top;
}

//----------------------------------------------------------------------------//
float RenderedStringComponent::getBottomPadding() const
{
    return d_padding.d_bottom;
}

//----------------------------------------------------------------------------//
void RenderedStringComponent::setAspectLock(const bool setting)
{
    d_aspectLock = setting;
}

//----------------------------------------------------------------------------//
bool RenderedStringComponent::getAspectLock() const
{
    return d_aspectLock;
}


//----------------------------------------------------------------------------//
RenderedStringTextComponent::RenderedStringTextComponent() :
d_font(0),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)
{
}

//----------------------------------------------------------------------------//
RenderedStringTextComponent::RenderedStringTextComponent(const String& text) :
d_text(text),
d_font(0),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)
{
}

//----------------------------------------------------------------------------//
RenderedStringTextComponent::RenderedStringTextComponent(const String& text,
														 hgeFont* font) :
d_text(text),
d_font(font),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)
{
}

//----------------------------------------------------------------------------//
RenderedStringTextComponent::RenderedStringTextComponent(const String& text,
														 const String& font_name) :
d_text(text),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)
{
	d_font = &FontManager::getSingleton().getFont(font_name);
}


//----------------------------------------------------------------------------//
void RenderedStringTextComponent::setText(const String& text)
{
	d_text = text;
}

//----------------------------------------------------------------------------//
const String& RenderedStringTextComponent::getText() const
{
	return d_text;
}

//----------------------------------------------------------------------------//
void RenderedStringTextComponent::setFont(hgeFont* font)
{
	d_font = font;
}

//----------------------------------------------------------------------------//
hgeFont* RenderedStringTextComponent::getFont() const
{
	return d_font;
}

//----------------------------------------------------------------------------//
void RenderedStringTextComponent::setColours(const ColourRect& cr)
{
	d_colours = cr;
}

//----------------------------------------------------------------------------//
void RenderedStringTextComponent::setColours(const colour& c)
{
	d_colours.setColours(c);
}

//----------------------------------------------------------------------------//
const ColourRect& RenderedStringTextComponent::getColours() const
{
	return d_colours;
}

//----------------------------------------------------------------------------//
void RenderedStringTextComponent::draw(GeometryBuffer& buffer,
									   const Vector2& position,
									   const ColourRect* mod_colours,
									   const hgeRect* clip_rect,
									   const float vertical_space,
									   const float space_extra) const
{
	hgeFont* fnt = d_font ? d_font : System::getSingleton().getDefaultFont();

	if (!fnt)
		return;

	Vector2 final_pos(position);
	float y_scale = 1.0f;

	// handle formatting options
	switch (d_verticalFormatting)
	{
	case VF_BOTTOM_ALIGNED:
		final_pos.d_y += vertical_space - getPixelSize().d_y;
		break;

	case VF_CENTRE_ALIGNED:
		final_pos.d_y += (vertical_space - getPixelSize().d_y) / 2 ;
		break;

	case VF_STRETCHED:
		y_scale = vertical_space / getPixelSize().d_y;
		break;

	case VF_TOP_ALIGNED:
		// nothing additional to do for this formatting option.
		break;

	default:
		throw InvalidRequestException("RenderedStringTextComponent::draw: "
			"unknown VerticalFormatting option specified.");
	}

	// apply padding to position:
	final_pos += d_padding.getPosition();

	// apply modulative colours if needed.
	ColourRect final_cols(d_colours);
	if (mod_colours)
		final_cols *= *mod_colours;

	// draw the text string.
	fnt->drawText(buffer, d_text, final_pos, clip_rect, final_cols,
		space_extra, 1.0f, y_scale);
}

//----------------------------------------------------------------------------//
Size RenderedStringTextComponent::getPixelSize() const
{
	hgeFont* fnt = d_font ? d_font : System::getSingleton().getDefaultFont();

	Size psz(d_padding.d_left + d_padding.d_right,
		d_padding.d_top + d_padding.d_bottom);

	if (fnt)
	{
		psz.d_x += fnt->getTextExtent(d_text);
		psz.d_y += fnt->getFontHeight();
	}

	return psz;
}

//----------------------------------------------------------------------------//
bool RenderedStringTextComponent::canSplit() const
{
	return true;
}

//----------------------------------------------------------------------------//
RenderedStringTextComponent* RenderedStringTextComponent::split(
	float split_point, bool first_component)
{
	hgeFont* fnt = d_font ? d_font : System::getSingleton().getDefaultFont();

	// This is checked, but should never fail, since if we had no font our
	// extent would be 0 and we would never cause a split to be needed here.
	if (!fnt)
		throw InvalidRequestException("RenderedStringTextComponent::split: "
		"unable to split with no font set.");

	// create 'left' side of split and clone our basic configuration
	RenderedStringTextComponent* lhs = new RenderedStringTextComponent;
	lhs->d_padding = d_padding;
	lhs->d_verticalFormatting = d_verticalFormatting;
	lhs->d_font = d_font;
	lhs->d_colours = d_colours;

	// calculate the 'best' place to split the text
	size_t left_len = 0;
	float left_extent = 0.0f;

	while (left_len < d_text.length())
	{
		size_t token_len = getNextTokenLength(d_text, left_len);
		// exit loop if no more valid tokens.
		if (token_len == 0)
			break;

		const float token_extent = 
			fnt->getTextExtent(d_text.substr(left_len, token_len));

		// does the next token extend past the split point?
		if (left_extent + token_extent > split_point)
		{
			// if it was the first token, split the token itself
			if (first_component && left_len == 0)
				left_len = fnt->getCharAtPixel(d_text.substr(0, token_len),	split_point);

			// left_len is now the character index at which to split the line
			break;
		}

		// add this token to the left side
		left_len += token_len;
		left_extent += token_extent;
	}

	// perform the split.
	lhs->d_text = d_text.substr(0, left_len);

	// here we're trimming leading delimiters from the substring range 
	size_t rhs_start =
		d_text.find_first_not_of(TextUtils::DefaultWrapDelimiters, left_len);
	if (rhs_start == String::npos)
		rhs_start = left_len;

	d_text = d_text.substr(rhs_start);

	return lhs;
}

//----------------------------------------------------------------------------//
size_t RenderedStringTextComponent::getNextTokenLength(const String& text,
													   size_t start_idx)
{
	String::size_type word_start =
		text.find_first_not_of(TextUtils::DefaultWrapDelimiters, start_idx);

	if (word_start == String::npos)
		word_start = start_idx;

	String::size_type word_end =
		text.find_first_of(TextUtils::DefaultWrapDelimiters, word_start);

	if (word_end == String::npos)
		word_end = text.length();

	return word_end - start_idx;
}

//----------------------------------------------------------------------------//
RenderedStringTextComponent* RenderedStringTextComponent::clone() const
{
	RenderedStringTextComponent* c = new RenderedStringTextComponent(*this);
	return c;
}

//----------------------------------------------------------------------------//
size_t RenderedStringTextComponent::getSpaceCount() const
{
	// TODO: The value calculated here is a good candidate for caching.

	size_t space_count = 0;

	// Count the number of spaces in this component.
	// NB: here I'm not countng tabs since those are really intended to be
	// something other than just a bigger space.
	const size_t char_count = d_text.length();
	for (size_t c = 0; c < char_count; ++c)
		if (d_text[c] == ' ') // TODO: There are other space characters!
			++space_count;

	return space_count;
}


//----------------------------------------------------------------------------//
RenderedStringImageComponent::RenderedStringImageComponent() :
d_image(0),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF),
d_size(0, 0)
{
}

//----------------------------------------------------------------------------//
RenderedStringImageComponent::RenderedStringImageComponent(const Image* image) :
d_image(image),
d_colours(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF),
d_size(0, 0)
{
}

//----------------------------------------------------------------------------//
void RenderedStringImageComponent::setImage(const Image* image)
{
	d_image = image;
}

//----------------------------------------------------------------------------//
const Image* RenderedStringImageComponent::getImage() const
{
	return d_image;
}

//----------------------------------------------------------------------------//
void RenderedStringImageComponent::setColours(const ColourRect& cr)
{
	d_colours = cr;
}

//----------------------------------------------------------------------------//
void RenderedStringImageComponent::setColours(const colour& c)
{
	d_colours.setColours(c);
}

//----------------------------------------------------------------------------//
const ColourRect& RenderedStringImageComponent::getColours() const
{
	return d_colours;
}

//----------------------------------------------------------------------------//
void RenderedStringImageComponent::draw(GeometryBuffer& buffer,
										const Vector2& position,
										const ColourRect* mod_colours,
										const hgeRect* clip_rect,
										const float vertical_space,
										const float /*space_extra*/) const
{
	if (!d_image)
		return;

	hgeRect dest(position.d_x, position.d_y, 0, 0);
	float y_scale = 1.0f;

	// handle formatting options
	switch (d_verticalFormatting)
	{
	case VF_BOTTOM_ALIGNED:
		dest.d_top += vertical_space - getPixelSize().d_y;
		break;

	case VF_CENTRE_ALIGNED:
		dest.d_top += (vertical_space - getPixelSize().d_y) / 2 ;
		break;

	case VF_STRETCHED:
		y_scale = vertical_space / getPixelSize().d_y;
		break;

	case VF_TOP_ALIGNED:
		// nothing additional to do for this formatting option.
		break;

	default:
		throw InvalidRequestException("RenderedStringImageComponent::draw: "
			"unknown VerticalFormatting option specified.");
	}

	Size sz(d_image->getSize());
	if (d_size.d_x != 0.0)
		sz.d_x = d_size.d_x;
	if (d_size.d_y != 0.0)
		sz.d_y = d_size.d_y;

	sz.d_y *= y_scale;
	dest.setSize(sz);

	// apply padding to position
	dest.offset(d_padding.getPosition());

	// apply modulative colours if needed.
	ColourRect final_cols(d_colours);
	if (mod_colours)
		final_cols *= *mod_colours;

	// draw the image.
	d_image->draw(buffer, dest, clip_rect, final_cols);
}

//----------------------------------------------------------------------------//
Size RenderedStringImageComponent::getPixelSize() const
{
	Size sz(0, 0);

	if (d_image)
	{
		sz = d_image->getSize();
		if (d_size.d_x != 0.0)
			sz.d_x = d_size.d_x;
		if (d_size.d_y != 0.0)
			sz.d_y = d_size.d_y;
		sz.d_x += (d_padding.d_left + d_padding.d_right);
		sz.d_y += (d_padding.d_top + d_padding.d_bottom);
	}

	return sz;
}

//----------------------------------------------------------------------------//
bool RenderedStringImageComponent::canSplit() const
{
	return false;
}

//----------------------------------------------------------------------------//
RenderedStringImageComponent* RenderedStringImageComponent::split(
	float /*split_point*/, bool /*first_component*/)
{
	throw InvalidRequestException("RenderedStringImageComponent::split: this "
		"component does not support being split.");
}

//----------------------------------------------------------------------------//
RenderedStringImageComponent* RenderedStringImageComponent::clone() const
{
	return new RenderedStringImageComponent(*this);
}

//----------------------------------------------------------------------------//
size_t RenderedStringImageComponent::getSpaceCount() const
{
	// images do not have spaces.
	return 0;
}

//----------------------------------------------------------------------------//
void RenderedStringImageComponent::setSize(const Size& sz)
{
	d_size = sz;
}

//----------------------------------------------------------------------------//
const Size& RenderedStringImageComponent::getSize() const
{
	return d_size;
}

//----------------------------------------------------------------------------//
RenderedStringWidgetComponent::RenderedStringWidgetComponent() :
d_window(0)
{
}

//----------------------------------------------------------------------------//
RenderedStringWidgetComponent::RenderedStringWidgetComponent(
	const String& widget_name) :
d_window(WindowManager::getSingleton().getWindow(widget_name))
{
}

//----------------------------------------------------------------------------//
RenderedStringWidgetComponent::RenderedStringWidgetComponent(Window* widget) :
d_window(widget)
{
}

//----------------------------------------------------------------------------//
void RenderedStringWidgetComponent::setWindow(const String& widget_name)
{
	d_window = WindowManager::getSingleton().getWindow(widget_name);
}

//----------------------------------------------------------------------------//
void RenderedStringWidgetComponent::setWindow(Window* widget)
{
	d_window = widget;
}

//----------------------------------------------------------------------------//
const Window* RenderedStringWidgetComponent::getWindow() const
{
	return d_window;
}

//----------------------------------------------------------------------------//
void RenderedStringWidgetComponent::draw(GeometryBuffer& /*buffer*/,
										 const Vector2& position,
										 const ColourRect* /*mod_colours*/,
										 const hgeRect* /*clip_rect*/,
										 const float vertical_space,
										 const float /*space_extra*/) const
{
	if (!d_window)
		return;

	// HACK: re-adjust for inner-rect of parent
	float x_adj = 0, y_adj = 0;
	Window* parent = d_window->getParent();

	if (parent)
	{
		const hgeRect outer(parent->getUnclippedOuterRect());
		const hgeRect inner(parent->getUnclippedInnerRect());
		x_adj = inner.d_left - outer.d_left;
		y_adj = inner.d_top - outer.d_top;
	}
	// HACK: re-adjust for inner-rect of parent (Ends)

	Vector2 final_pos(position);
	// handle formatting options
	switch (d_verticalFormatting)
	{
	case VF_BOTTOM_ALIGNED:
		final_pos.d_y += vertical_space - getPixelSize().d_y;
		break;

	case VF_STRETCHED:
		hgeLogger::getSingleton().logEvent("RenderedStringWidgetComponent::draw: "
			"VF_STRETCHED specified but is unsupported for Widget types; "
			"defaulting to VF_CENTRE_ALIGNED instead.");

		// intentional fall-through.

	case VF_CENTRE_ALIGNED:
		final_pos.d_y += (vertical_space - getPixelSize().d_y) / 2 ;
		break;


	case VF_TOP_ALIGNED:
		// nothing additional to do for this formatting option.
		break;

	default:
		throw InvalidRequestException("RenderedStringTextComponent::draw: "
			"unknown VerticalFormatting option specified.");
	}

	// we do not actually draw the widget, we just move it into position.
	d_window->setPosition((UINT)(final_pos.d_x + d_padding.d_left - x_adj), (UINT)(final_pos.d_y + d_padding.d_top - y_adj));
}

//----------------------------------------------------------------------------//
Size RenderedStringWidgetComponent::getPixelSize() const
{
	Size sz(0, 0);

	if (d_window)
	{
		sz = d_window->getPixelSize();
		sz.d_x += (d_padding.d_left + d_padding.d_right);
		sz.d_y += (d_padding.d_top + d_padding.d_bottom);
	}

	return sz;
}

//----------------------------------------------------------------------------//
bool RenderedStringWidgetComponent::canSplit() const
{
	return false;
}

//----------------------------------------------------------------------------//
RenderedStringWidgetComponent* RenderedStringWidgetComponent::split(
	float /*split_point*/, bool /*first_component*/)
{
	throw InvalidRequestException("RenderedStringWidgetComponent::split: this "
		"component does not support being split.");
}

//----------------------------------------------------------------------------//
RenderedStringWidgetComponent* RenderedStringWidgetComponent::clone() const
{
	return new RenderedStringWidgetComponent(*this);
}

//----------------------------------------------------------------------------//
size_t RenderedStringWidgetComponent::getSpaceCount() const
{
	// widgets do not have spaces
	return 0;
}